/**
 * ex2. 数据类型
 * 	基本数据类型：整形，浮点型，布尔型，字符串
 *	复合数据类型：数组，切片，结构体，函数，map，通道（channel）， 接口等
 *
 * @author cyy
 * @date 2020/10/26
 */
package main

import (
	"bytes"
	"fmt"
	"strconv"
	"strings"
	"unicode/utf8"
	"unsafe"
)

func main() {
	/*
		整形 int，默认值为0
			有符号整型：int8  int16  int32  int64
			无符号整型：unit8 unit16 unit32 unit64
			go中默认的int取决于操作系统字长
	*/
	fmt.Println("============================ 整形 ============================")
	var a0 int8
	var a1 int16
	var a2 int
	// 可以使用unsafe包下的sizeof函数来查看变量被分配的内存大小，以字节为单位
	fmt.Println("int8占用字节数：\t", unsafe.Sizeof( /*int8(0)*/ a0))   // 1
	fmt.Println("int16占用字节数：\t", unsafe.Sizeof( /*int16(0)*/ a1)) // 2
	fmt.Println("int占用字节数：\t", unsafe.Sizeof( /*int(0)*/ a2))     // 8
	// 注：go不存在隐式向上转型一说，必须手动显式向上转型
	// fmt.Println(a0 + a1) // invalid operation: a0 + a1 (mismatched types int8 and int16)
	fmt.Println("go不存在隐式向上转型一说，必须手动显式向上转型：int16(a0) + a1 = ", int16(a0)+a1)

	/*
		数字字面量语法，直接使用二进制、八进制或十六进制浮点数的格式定义数字
			%d：10进制，%b：2进制，%o：8进制，%x：16进制
	*/
	fmt.Println("\n============================ 数字字面量语法 ============================")
	fmt.Printf("10进制 - %d\n", 17)
	fmt.Printf("0进制 - %b\n", 17)
	fmt.Printf("8进制 - %o\n", 17)
	fmt.Printf("16进制 - %x\n", 17)

	/*
		浮点类型 float，默认值为0
			float32，float64
			默认的float取决于操作系统字长
	*/
	fmt.Println("\n============================ 浮点类型 ============================")
	var b = 3.1415926
	fmt.Printf("b = %v -- %f，保留两位小数后%.2f\n", b, b, b)

	// 精度丢失问题
	// 	float存在精度丢失问题
	var f1 float64 = 8.2
	var f2 float64 = 3.8
	fmt.Printf("精度丢失问题：8.2 - 3.8 = %v\n", f1-f2) // 4.3999999999999995
	// TODO: 2020/10/26 精度丢失问题解决

	/*
		布尔值bool，默认值为false
			使用Printf打印时对应%t
	*/
	fmt.Println("\n============================ 布尔值bool ============================")
	x := false
	fmt.Printf("值：%v %t，类型%T\n", x, x, x)

	/*
		字符（非基本数据类型） - byte、rune
			go语言中的字符有两种类型
				- uint8类型/byte类型：代表了ascii码的一个字符
				- rune类型：代表一个utf-8字符，实际上是一个int32
			go使用了特殊的rune类型来处理unicode，让基于unicode的文本处理更为方便
			对应的格式化输出字符为%c，若使用%v将会输出编码后的int32值
	*/
	fmt.Println("\n============================ 字符 ============================")
	c0 := '字'
	c1 := 'a'
	// 使用utf-8编码，编码后的值为int32
	fmt.Printf("值：%c，unicode编码10进制：%v，类型：%T，占用字节数：%d\n", c0, c0, c0, unsafe.Sizeof(c0)) // 字	23383	int32	4
	fmt.Printf("值：%c，unicode编码10进制：%v，类型：%T，占用字节数：%d\n", c1, c1, c1, unsafe.Sizeof(c1)) // a	97		int32	4

	fmt.Printf("兼容ASKII码 a - 97：%c\n", 97)

	// var b0 byte = '符' // constant 31526 overflows byte，四字节分配给一字节 => 溢出
	var b1 byte = 'a'
	var b2 = '符'
	fmt.Println(b1)
	fmt.Println(b2)

	/*
		字符串string，默认值为空串 —— ""
			底层实现为byte数组
	*/
	fmt.Println("\n============================ 字符串 ============================")
	// 可以使用反引号（``）来定义多行字符串
	var c = `使用反引号来定义多行字符串
	line1
			 	line2`
	fmt.Println(c)

	// 字符串的操作
	fmt.Println("---------------------------- 字符串的操作 ----------------------------")
	d1 := "d1"
	d2 := "d2"
	// 	- 可以通过运算符“+”进行字符串的连接
	// 	- 也可以通过fmt包下的 Sprintf(format string, a ...interface{}) 函数
	d3 := fmt.Sprintf("%v%v", d1, d2)
	fmt.Println("通过fmt包下的 Sprintf 函数实现字符串的拼接：", d3)
	fmt.Println("通过运算符“+”实现字符串的拼接：", d1+d2)
	// 关于字符串的操作可以查看strings包
	fmt.Println("使用strings包中的HasPrefix函数：", strings.HasPrefix("abcd", "ab")) // true

	// 字符串的长度
	fmt.Println("\n---------------------------- 字符串的长度 ----------------------------")
	// - 通过len(v Type) ×
	// 	注：通过len函数来获取长度时获取的其实是该string在内存中占用的字节数，
	// 	而字符串底层是一个byte数组，所以使用len()函数获取到的其实是这个数组的长度，而在utf-8中一个中文就占3个字节，所以使用该种方式是不合理的。
	e := "你好"
	fmt.Println("len()：", len(e)) // 6
	// - 先转化为rune数组，再通过len()函数获取rune数组的长度
	runeE := []rune(e)
	fmt.Println("rune数组的长度：", len(runeE)) // 2
	// - 通过 bytes.Count() 统计
	fmt.Println("通过 bytes.Count() 统计：", bytes.Count([]byte(e), nil)-1)
	// - 通过 strings.Count() 统计
	fmt.Println("通过 strings.Count() 统计：", strings.Count(e, "")-1)
	// - 通过 utf8.RuneCountInString() 统计
	fmt.Println("通过 utf8.RuneCountInString() 统计：", utf8.RuneCountInString(e))

	// 字符串的修改
	fmt.Println("\n---------------------------- 字符串的修改 ----------------------------")
	// 	必须先将string转换成[]byte或者[]rune，修改完成后再转换回string，无论哪种转换，都会重新分配内存并复制字节数组。
	str := "你好，golang"
	fmt.Println("修改前的字符串：", str)
	runeStr := []rune(str)
	fmt.Println("rune数组：", runeStr)
	runeStr[0] = '您'
	fmt.Println("修改后的字符串：", string(runeStr))

	// 字符串的遍历
	fmt.Println("\n---------------------------- 字符串的遍历 ----------------------------")
	// - 以byte形式遍历 —— 使用len()获取长度，再通过for循环进行遍历 × => 只适用于仅包含askii字符的字符串
	//		228-ä 189-½ 160-  229-å 165-¥ 189-½ 239-ï 188-¼ 140- 103-g 111-o 108-l 97-a 110-n 103-g
	fmt.Print("以byte形式遍历：")
	for i := 0; i < len(str); i++ {
		fmt.Printf("%v-%c\t", str[i], str[i])
	}
	fmt.Println()
	// - 以rune形式遍历（range关键字） —— 类似java的foreach ✔
	// 		20320-你	22909-好	65292-，	103-g	111-o	108-l	97-a	110-n	103-g
	fmt.Print("以rune形式遍历：")
	for _, v := range str { // 若需要使用下标 => for i, v := range str
		fmt.Printf("%v-%c\t", v, v)
	}
	fmt.Println()

	/*
		类型转换
			java：(目标类型)源数据
			go：除string外的基本数据类型 —— 目标类型(源数据)
	*/
	fmt.Println("\n============================ 类型转换 ============================")
	// string与其他基本数据类型之间的转换
	fmt.Println("---------------------------- 其他基本数据类型转string ----------------------------")

	// - 通过fmt.Sprintf() —— %d - int，%f - float，%t - bool，%c - 字符
	fmt.Println("通过fmt.Sprintf()：")
	str1 := fmt.Sprintf("%d", 1)
	fmt.Printf("int转string —— 值：%v，类型：%T\n", str1, str1) // 值：1，类型：string
	str2 := fmt.Sprintf("%.2f", 2.2)
	fmt.Printf("float转string —— 值：%v，类型：%T\n", str2, str2) // 值：2.20，类型：string
	str3 := fmt.Sprintf("%t", false)
	fmt.Printf("bool转string —— 值：%v，类型：%T\n", str3, str3) // 值：false，类型：string
	str4 := fmt.Sprintf("%c", 'a')
	fmt.Printf("字符转string —— 值：%v，类型：%T\n", str4, str4) // 值：a，类型：string

	// - 通过string()函数，只能将字符或者通过编码后的int32码、rune数组转换为字符串，
	fmt.Println("\n通过string()：")
	fmt.Printf("编码值转string —— %v - %T\n", string(189), string(189))
	fmt.Printf("rune转string —— %v - %T\n", string(rune(189)), string(rune(189)))
	fmt.Printf("rune数组转string —— %v - %T\n", string([]rune{20320, 22909, 65292, 103, 111, 108, 97, 110, 103}), string(rune(189)))

	// - 通过strconv包的format函数（以int和float为例）
	fmt.Println("\n通过strconv包的format函数：")
	/*
		- FormatInt
			- 参数1：要转换的值
			- 参数2：参数1的进制类型
		- FormatFloat
			- 参数1：要转换的值
			- 参数2：格式化类型 'f' (-ddd.dddd, no exponent)，更多的格式参考源码中注释
			- 参数3：保留的小数点 -1表示不对小数格式化
			- 参数4：格式化的类型 32 64
	*/
	var str5 = strconv.FormatInt(int64(1), 10)
	fmt.Printf("int转string —— 值：%v，类型：%T\n", str5, str5)
	var str6 = strconv.FormatFloat(1.1, 'f', 4, 64)
	fmt.Printf("float转string —— 值：%v，类型：%T\n", str6, str6)

	// string转其他类型
	fmt.Println("\n---------------------------- string转其他类型 ----------------------------")
	// 	通过strconv包的parse函数（以int和float为例）
	fmt.Println("通过strconv包的parse函数：")
	int0, _ := strconv.ParseInt("1", 10, 64)
	fmt.Printf("string转int64 —— 值：%v，类型：%T\n", int0, int0)
	float0, _ := strconv.ParseFloat("1.1", 64)
	fmt.Printf("string转float64 —— 值：%v，类型：%T\n", float0, float0)
	// 		第二个返回结果是err
	int1, err := strconv.ParseInt("-", 10, 64)
	fmt.Printf("转换失败的后的int64 —— 值：%v，类型：%T\n", int1, int1)
	fmt.Printf("转换失败的后的err —— 值：%v，类型：%T\n", err, err)

	/*
		运算符
			其他算术运算符、关系运算符、逻辑运算符和赋值运算符（+=、-=、...）用法均相同
			go中的自增（++）自减（--）操作与java的区别，以i++为例
				1. 是单独的语句而不是运算符，无法返回i的值
				2. 没有--i一说
		if-else
			与 java的唯一区别是go中可以省略条件的括号
		while
			go中不存在while循环，可以使用for来代替
	*/

	/*
		switch
	*/
	fmt.Println("\n============================ switch ============================")
	// 无需使用break明确退出case块
	// 分支可以使用表达式，这时候switch后不需要再紧跟判断变量
	//	执行结果：1
	tmp := 8
	switch /* 此处无需添加判断变量 */ {
	case tmp < 10:
		fmt.Println(1)
	case tmp < 20:
		fmt.Println(2)
	default:
		fmt.Println(3)
	}

	/*
		fallthrough 关键字 —— 穿透
			该关键字可以执行满足条件的case的下一个case，是为了兼容c这种的case而设计的
	*/
	fmt.Println("\n---------------------------- fallthrough ----------------------------")
	// 执行结果：
	// 	2 case = true
	// 	3 case = false
	// 	4 case = true
	/*	switch {
		case false:
			fmt.Println("1 case = false")
			fallthrough
		case true:
			fmt.Println("2 case = true")
			fallthrough // 穿透
		case false:
			fmt.Println("3 case = false")
			fallthrough
		case true:
			fmt.Println("4 case = true")
		case false:
			fmt.Println("5 case = false")
			fallthrough
		default:
			fmt.Println("default")
		}
	*/

	/*
		break、continue与java一致
		goto —— 无条件跳转
			通过标签进行代码间的无条件跳转，实现快速跳出循环、避免重复退出
	*/
	fmt.Println("\n============================ goto ============================")
	n := 3
	if n > 2 {
		fmt.Print("跳转")
		goto label
	}
	fmt.Println(1)
label:
	fmt.Println(2)

}
